package com.shisan.tingshu.user.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.shisan.tingshu.album.client.AlbumInfoFeignClient;
import com.shisan.tingshu.common.constant.PublicConstant;
import com.shisan.tingshu.common.constant.RedisConstant;
import com.shisan.tingshu.common.constant.SystemConstant;
import com.shisan.tingshu.common.execption.ShisanException;
import com.shisan.tingshu.common.rabbit.constant.MqConst;
import com.shisan.tingshu.common.rabbit.service.RabbitService;
import com.shisan.tingshu.common.result.Result;
import com.shisan.tingshu.common.util.AuthContextHolder;
import com.shisan.tingshu.common.util.MongoUtil;
import com.shisan.tingshu.model.user.*;
import com.shisan.tingshu.user.mapper.UserInfoMapper;
import com.shisan.tingshu.user.mapper.UserPaidAlbumMapper;
import com.shisan.tingshu.user.mapper.UserPaidTrackMapper;
import com.shisan.tingshu.user.service.UserInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shisan.tingshu.vo.album.TrackListVo;
import com.shisan.tingshu.vo.album.TrackStatMqVo;
import com.shisan.tingshu.vo.user.UserCollectVo;
import com.shisan.tingshu.vo.user.UserInfoVo;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

	@Autowired
	private UserInfoMapper userInfoMapper;

	@Autowired
	private UserPaidTrackMapper userPaidTrackMapper;

	@Autowired
	private UserPaidAlbumMapper userPaidAlbumMapper;

	@Autowired
	private WxMaService wxMaService;

	@Autowired
	private RsaSigner rsaSigner;

	@Autowired
	private StringRedisTemplate redisTemplate;

	@Autowired
	private MongoTemplate mongoTemplate;

	// RabbitService是自己定义的接口，在common包下的rabbit-util微服务中
	@Autowired
	private RabbitService rabbitService;

	@Autowired
	private AlbumInfoFeignClient albumInfoFeignClient;

	/**
	 * 微信小程序登录
	 * @param code
	 * @return
	 */
	@Override
	public Map<String, Object> wxLogin(String code) {

		HashMap<String, Object> map = new HashMap<>();

		String openid = "";
		// 判断code码是否存在
		if (code == null) {
			throw  new ShisanException(201, "code码不存在");
		}

		// 调用微信服务端
		WxMaUserService userService = wxMaService.getUserService();
		WxMaJscode2SessionResult sessionInfo = null;
		try {
			sessionInfo = userService.getSessionInfo(code);
			openid = sessionInfo.getOpenid();
		} catch (WxErrorException e) {
			log.error("微信小程序登录失败：{}", e.getMessage());
			throw new ShisanException(201, "微信小程序登录失败");
		}

		// 根据openId从Redis中查询用户信息，有则直接返回，没有才继续执行下面的查数据库逻辑
		String refreshTokenKey = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openid;
		String jsonWebTokenFromRedis = redisTemplate.opsForValue().get(refreshTokenKey);
		if (!StringUtils.isEmpty(jsonWebTokenFromRedis)) {
			Map<String, Object> map1 = new HashMap<>();
			map1.put("token", jsonWebTokenFromRedis);
			return map1;
		}

		// 根据openId从数据库中查询用户信息
		LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(UserInfo::getWxOpenId, openid);
		UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
		if (userInfo == null) {
			// 向user_info表中插入用户（注册用户信息）
			userInfo = new UserInfo();
			userInfo.setWxOpenId(openid);
			userInfo.setNickname(System.currentTimeMillis() + "-【tingshu】-" + UUID.randomUUID().toString().substring(0, 4).replace("-", ""));
			userInfo.setAvatarUrl("https://oss.aliyuncs.com/aliyun_id_photo_bucket/default_handsome.jpg");
			userInfo.setIsVip(0); // 默认不是会员
			userInfo.setVipExpireTime(new Date());
			int insert = userInfoMapper.insert(userInfo);
			log.info("注册用户：{}", insert > 0 ? "成功" : "失败");

			// 向tingshu_account库中的user_account表中插入用户账户（初始化用户账户余额）
			// 传递的参数：交换机（MqConst是自定义的）、路由键、消息内容
			// 注意：在网络传输中为了安全，传输的数据格式最好是字符串，所以下面将id转为字符串
			rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, userInfo.getId().toString());
			log.info("service-user微服务发送初始化{}用户账户余额信息成功", userInfo.getId());
		}

		// String token = UUID.randomUUID().toString().replace("-", ""); // 原始 Token
		// 得到 jsonWebToken
		String token = getJsonWebToken(userInfo.getId(), openid);
		map.put("token", token);
		// map.put("refreshToken", token); 因为后端只返回accessToken，所以此行注释掉

		// 将 jsonWebToken 放到Redis中
		// Reids 常用数据类型有 5 种：String List Set Zset Hash
		// 定义 key
		String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openid;

		// 下面的 v 一般来说是要设置成不一致的。比如accessTokenKey的v是UUID，refreshTokenKey的v是JWT。但此处不做设置
		redisTemplate.opsForValue().set(accessTokenKey, token, 20, TimeUnit.DAYS); // k-v
		redisTemplate.opsForValue().set(refreshTokenKey, token, 20, TimeUnit.DAYS);

		return map;
	}

	private String getJsonWebToken(Long userId, String openid) {
		// 定义一个载荷
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("id", userId);
		jsonObject.put("openId", openid);
		// 生成jwt
		// RSA 是一种非对称加密方式，它会提供一把公钥（用于加密和验签）一把私钥（用于解密和加签）
		// 加密：为了防止数据被篡改
		// 加签：将数据转成密文
		Jwt jwt = JwtHelper.encode(jsonObject.toString(), rsaSigner); // 传入私钥用于加签
		String encoded = jwt.getEncoded();
		return encoded;
	}

	/**
	 * 获取新令牌
	 * @return
	 */
	@Override
	public Map<String, Object> getNewAccessToken() {

		HashMap<String, Object> result = new HashMap<>();

		// 获取目标请求属性对象
		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		// 获取请求对象
		HttpServletRequest request = requestAttributes.getRequest();
		// 获取请求头
		String token = request.getHeader("token");
		if(StringUtils.isEmpty(token)) {
			throw new ShisanException(201, "之前没有登录过");
		}
		// 校验jsonWebToken是否有被篡改。传入token和公钥（验签）。若被篡改会自动抛出异常，没有则往下继续执行
		Jwt jwt = JwtHelper.decodeAndVerify(token  , new RsaVerifier(PublicConstant.PUBLIC_KEY));
		// 校验通过，获取载荷数据
		String claims = jwt.getClaims();
		Map map = JSONObject.parseObject(claims, Map.class);
		Object userId = map.get("id");
		Object openid = map.get("openId");
		String refreshTokenKey = RedisConstant.USER_LOGIN_REFRESH_KEY_PREFIX + openid;
		String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openid;
		// 从Redis中获取refreshToken
		String refreshToken = redisTemplate.opsForValue().get(refreshTokenKey);
		if(!StringUtils.isEmpty(refreshToken)) {
			// 将Reids中的accessToken和refreshToken都更新为新生成的accessToken
			String jsonWebToken = getJsonWebToken(Long.parseLong(userId.toString()), String.valueOf(openid.toString()));
			redisTemplate.opsForValue().set(accessTokenKey, jsonWebToken, 20, TimeUnit.DAYS);
			redisTemplate.opsForValue().set(refreshTokenKey, jsonWebToken, 20, TimeUnit.DAYS);
			// 返回新accessToken
			result.put("token", jsonWebToken);
			return result;
		} else {
			// 去登录
			result.put("1", "v");
		}

		return result;
	}

	/**
	 * 更新用户信息
	 * @param userInfoVo
	 */
	@Override
	public void updateUser(UserInfoVo userInfoVo) {
		// 查询用户信息
		Long userId = AuthContextHolder.getUserId();
		UserInfo userInfo = userInfoMapper.selectById(userId);
		if (null == userInfo) {
			throw new ShisanException(201, "用户不存在");
		}
		// 更新用户信息
		userInfo.setNickname(userInfoVo.getNickname());
		userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
		userInfoMapper.updateById(userInfo);
	}

	/**
	 * 根据ID获取用户信息
	 * @param userId
	 * @return
	 */
	@Override
	public UserInfoVo getUserInfo(Long userId) {
		UserInfo userInfo = userInfoMapper.selectById(userId);
		if (userInfo == null){
			throw new ShisanException(201, "用户不存在");
		}
		UserInfoVo userInfoVo = new UserInfoVo();
		BeanUtils.copyProperties(userInfo, userInfoVo);
		return userInfoVo;
	}

	/**
	 * 查询当前用户购买过当前专辑下的哪些声音
	 * @param userId
	 * @param albumId
	 * @return
	 */
	@Override
	public Map<Long, String> getUserPaidAlbumTrack(Long userId, Long albumId) {
		LambdaQueryWrapper<UserPaidTrack> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(UserPaidTrack::getUserId, userId);
		wrapper.eq(UserPaidTrack::getAlbumId, albumId);
		List<UserPaidTrack> userPaidTracks = userPaidTrackMapper.selectList(wrapper);
		// 用1来表示购买过，0表示未购买
		Map<Long, String> map = userPaidTracks.stream().collect(Collectors.toMap(UserPaidTrack::getTrackId, v -> "1"));

		return map;
	}

	/**
	 * 查询用户是否购买过整张专辑
	 * @param userId
	 * @param albumId
	 * @return
	 */
	@Override
	public Boolean getUserPaidAlbum(Long userId, Long albumId) {
		LambdaQueryWrapper<UserPaidAlbum> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(UserPaidAlbum::getUserId, userId);
		wrapper.eq(UserPaidAlbum::getAlbumId, albumId);
		UserPaidAlbum userPaidAlbum = userPaidAlbumMapper.selectOne(wrapper);

		return userPaidAlbum != null;
	}

	/**
	 * 收藏与取消收藏声音
	 * @param trackId
	 * @return
	 */
	@Override
	public Boolean collect(Long trackId) {

		Long userId = AuthContextHolder.getUserId();
		// 构建条件对象
		Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);
		// 构建查询对象
		Query query = new Query(criteria);
		// 开始查询
		String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId);
		UserCollect userCollect = mongoTemplate.findOne(query, UserCollect.class, collectionName);
		if (userCollect == null) { // 未收藏过
			// 收藏声音。插入收藏对象到 MongoDB
			userCollect = new UserCollect();
			userCollect.setId(ObjectId.get().toString());
			userCollect.setUserId(userId);
			userCollect.setTrackId(trackId);
			userCollect.setCreateTime(new Date());
			mongoTemplate.save(userCollect, collectionName);
			TrackStatMqVo trackStatMqVo = prepareTrackStatMqDto(null, userCollect.getTrackId(), SystemConstant.TRACK_STAT_COLLECT, 1);
			rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
			return true;
		} else { // 已收藏过
			// 取消收藏
			mongoTemplate.remove(query, UserCollect.class, collectionName);
			TrackStatMqVo trackStatMqVo = prepareTrackStatMqDto(null, userCollect.getTrackId(), SystemConstant.TRACK_STAT_COLLECT, -1);
			rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, JSONObject.toJSONString(trackStatMqVo));
			return false;
		}
	}

	/**
	 * 查询用户是否收藏该声音
	 * @param trackId
	 * @return
	 */
	@Override
	public Boolean isCollect(Long trackId) {

		Long userId = AuthContextHolder.getUserId();
		// 构建条件对象
		Criteria criteria = Criteria.where("userId").is(userId).and("trackId").is(trackId);
		// 构建查询对象
		Query query = new Query(criteria);
		long count = mongoTemplate.count(query, UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
		return count > 0;
	}

	/**
	 * 查询用户是否订阅该专辑
	 * @param albumId
	 * @return
	 */
	@Override
	public Boolean isSubscribe(Long albumId) {

		Long userId = AuthContextHolder.getUserId();
		// 构建条件对象
		Criteria criteria = Criteria.where("userId").is(userId).and("albumId").is(albumId);
		// 构建查询对象
		Query query = new Query(criteria);
		long count = mongoTemplate.count(query, UserSubscribe.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_SUBSCRIBE, userId));
		return count > 0;
	}

	/**
	 * 准备TrackStatMqDto对象。该对象包含了需要发送到MQ的消息内容，用于更新声音的播放统计信息
	 * @param albumId
	 * @param trackId
	 * @param trackStatType
	 * @param count
	 * @return
	 */
	@Override
	public TrackStatMqVo prepareTrackStatMqDto(Long albumId, Long trackId, String trackStatType, int count) {
		TrackStatMqVo trackStatMqVo = new TrackStatMqVo();
		trackStatMqVo.setBusinessNo(UUID.randomUUID().toString().replace("-", "")); // 消息去重
		trackStatMqVo.setAlbumId(albumId);
		trackStatMqVo.setTrackId(trackId);
		trackStatMqVo.setStatType(trackStatType);
		trackStatMqVo.setCount(count);
		return trackStatMqVo;
	}

	/**
	 * 查询用户收藏过的声音列表分页展示
	 * @param pageParam
	 * @return
	 */
	@Override
	public IPage<UserCollectVo> findUserCollectPage(IPage<UserCollectVo> pageParam) {

		Long userId = AuthContextHolder.getUserId();
		// 构建条件对象
		Criteria criteria = Criteria.where("userId").is(userId);
		// 构建查询对象
		Query query = new Query(criteria);
		// 构建分页和排序条件对象
		Sort sort = Sort.by(Sort.Order.desc("updateTime"));
		PageRequest pageAndSort = PageRequest.of((int) ((pageParam.getCurrent() - 1) * pageParam.getSize()), (int) pageParam.getSize(), sort);
		query.with(pageAndSort);
		// 查询总记录数
		long count = mongoTemplate.count(query.limit(-1), UserCollect.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId));
		// 查询数据
		List<UserCollect> userCollectList = mongoTemplate.find(
				query,
				UserCollect.class,
				MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_COLLECT, userId)
		);
		// 从查询到的数据中，获取声音id集合
		List<Long> trackIdList = userCollectList.stream().map(UserCollect::getTrackId).collect(Collectors.toList());
		// 根据声音id集合，查询声音对象集合
		Result<List<TrackListVo>> trackListVoResult = albumInfoFeignClient.getTrackListByIds(trackIdList);
		List<TrackListVo> trackListVos = trackListVoResult.getData();
		if (CollectionUtils.isEmpty(trackListVos)) {
			throw new ShisanException(201, "远程查询专辑微服务，获取声音集合失败");
		}
		// 将声音对象的列表集合转成声音对象的Map集合
		Map<Long, TrackListVo> trackListVoMap = trackListVos.stream().collect(Collectors.toMap(TrackListVo::getTrackId, v -> v));
		List<UserCollectVo> userCollectVoList = userCollectList.stream().map(userCollect -> {
			UserCollectVo userCollectVo = new UserCollectVo();
			TrackListVo trackListVo = trackListVoMap.get(userCollect.getTrackId());
			userCollectVo.setAlbumId(trackListVo.getAlbumId()); // 收藏声音对应的专辑id
			userCollectVo.setTrackId(userCollect.getTrackId());
			userCollectVo.setCreateTime(userCollect.getCreateTime());
			userCollectVo.setTrackTitle(trackListVo.getTrackTitle());  // 收藏声音标题
			userCollectVo.setCoverUrl(trackListVo.getCoverUrl());  // 收藏声音的封面
			return userCollectVo;
		}).collect(Collectors.toList());
		return pageParam.setRecords(userCollectVoList).setTotal(count);
	}

	@Override
	public void updateExpireVip() {

		// 1.查询过期的vip
		LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.eq(UserInfo::getIsVip, 1);
		queryWrapper.lt(UserInfo::getVipExpireTime, new Date());
		queryWrapper.eq(UserInfo::getIsDeleted, 0);
		List<UserInfo> userInfos = userInfoMapper.selectList(queryWrapper);
		// 2.修改
		userInfos.stream().forEach(userInfo -> {
			userInfo.setIsVip(0);
			userInfoMapper.updateById(userInfo);
		});
	}
}